
using AutoMapper;
using Domain.Models;
using IRepository;
using IServices;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Services.Base;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using ViewModel.Auth;

namespace Services
{
    public class AuthTokenService : BaseService<AuthToken>, IAuthTokenService
    {
        protected readonly IAuthTokenRepository authtokenRepository;
        protected readonly IAccountService accountService;
        private readonly IConfiguration configuration;
        private readonly AuthConfiguration jwtConfig = new AuthConfiguration();
        public AuthTokenService(
            IAuthTokenRepository authtokenRepository,
            IAccountService accountService,
            IMapper mapper,
            IConfiguration configuration) : base(mapper)
        {
            this.authtokenRepository = authtokenRepository;
            this.accountService = accountService;
            base.baseRepository = authtokenRepository;
            this.configuration = configuration;
            configuration.GetSection("JWT").Bind(jwtConfig);
        }

        public bool AddToken(AccessToken accessToken)
        {
            var token = mapper.Map<AuthToken>(accessToken);
            return Add(token) > 0;
        }

        public string GenerateRefreshToken()
        {
            var randomNumber = new byte[32];
            using (var rng = RandomNumberGenerator.Create())
            {
                rng.GetBytes(randomNumber);
                return Convert.ToBase64String(randomNumber);
            }
        }

        public string GenerateToken(IEnumerable<Claim> claims, bool isRefresh = false)
        {
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecurityKey));
            var jwt = new JwtSecurityToken(
                issuer: jwtConfig.Issuer,
                audience: jwtConfig.Audience,
                claims: claims,
                notBefore: DateTime.UtcNow,
                expires: DateTime.UtcNow.AddMinutes(isRefresh ? jwtConfig.RefreshExpired : jwtConfig.Expired),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
            return new JwtSecurityTokenHandler().WriteToken(jwt);
        }

        public string GenerateToken(string username)
        {
            throw new NotImplementedException();
        }

        public ClaimsPrincipal GetPrincipalFromExpiredToken(string expiredToken)
        {
            if (!Query(x => x.AccessToken == expiredToken).Any())
            {
                throw new SecurityTokenException("Invalid Refresh Token");
            }
            var tokenParameters = new TokenValidationParameters
            {
                ValidateAudience = true,
                ValidAudience = jwtConfig.Audience,
                ValidateIssuer = true,
                ValidIssuer = jwtConfig.Issuer,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecurityKey)),
                ValidateLifetime = false
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            SecurityToken securityToken;
            var principal = tokenHandler.ValidateToken(expiredToken, tokenParameters, out securityToken);
            var jwtSecurityToken = securityToken as JwtSecurityToken;
            if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.OrdinalIgnoreCase))
            {
                throw new SecurityTokenException("Invalid Token");
            }
            return principal;
        }

        public bool Update(AccessTokenBase oldToken, AccessTokenBase newToken)
        {
            var token = Query(x => x.AccessToken == oldToken.Access_Token && x.RefreshToken == oldToken.Refresh_Token).FirstOrDefault();

            if (token != null)
            {
                token.AccessToken = newToken.Access_Token;
                token.RefreshToken = newToken.Refresh_Token;
                token.UpdateTime = DateTime.Now;
                return Update(token);
            }
            return false;
        }
    }

}